/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

package scenic;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;

import scenic.jni.Renderer;

/**
 * This brush paints a repeating pattern. The pattern is created
 * by repeating the given scene.
 */
public class Pattern extends Brush implements SceneParent {
	private double width;
	private double height;
	private SceneNode pattern;
	
	private static Image tileImage64 = new Image(64, 64, Format.A8R8G8B8, ImageOptions.RENDER_TARGET);

	/**
	 * Creates a pattern with the given width and height.
	 * @param width the width of the pattern.
	 * @param height the height of the pattern.
	 */
	public Pattern(double width, double height) {
		this.width = width;
		this.height = height;
	}

	/**
	 * Gets the width of the pattern.
	 */
	public double getWidth() {
		return width;
	}

	/**
	 * Sets the width of the pattern.
	 */
	public void setWidth(double width) {
		this.width = width;
		changed();
	}

	/**
	 * Gets the height of the pattern.
	 */
	public double getHeight() {
		return height;
	}

	/**
	 * Sets the height of the pattern.
	 */
	public void setHeight(double height) {
		this.height = height;
		changed();
	}

	/**
	 * Gets the scene that defines the pattern.
	 */
	public SceneNode getPattern() {
		return pattern;
	}

	/**
	 * Sets the scene that defines the pattern.
	 */
	public void setPattern(SceneNode pattern) {
		if(this.pattern != null && isVisible())
			this.pattern.removeVisibleParent(this);
		this.pattern = pattern;
		if(this.pattern != null && isVisible())
			this.pattern.addVisibleParent(this);
		changed();
	}
	
	void draw(DrawContext context, AffineTransform transform, Rectangle visibleArea) {
		drawUsingRepeat(context, transform, visibleArea);
	}
	
	private void drawUsingRepeat(DrawContext context, AffineTransform transform, Rectangle visibleArea) {
		DrawContext ctx = new DrawContext(context, Renderer.SURFACE_TYPE_COLOR);
		Image img = tileImage64;
		AffineTransform tm;
		
		tm = AffineTransform.getScaleInstance(img.getWidth() / width, img.getHeight() / height);

		ctx.context = context.renderer.createContextForImage(img.getId());
		ctx.renderer.beginScene(ctx.context);		
		context.renderer.color(ctx.context, 0.0f, 0.0f, 0.0f, 0.0f);
		context.renderer.clear(ctx.context);
		pattern.draw(ctx, tm, new Rectangle(0, 0, img.getWidth(), img.getHeight()));
		ctx.renderer.endScene(ctx.context);
		ctx.renderer.deleteContext(ctx.context);
		
		tm = new AffineTransform();
		tm.concatenate(transform);
		tm.scale(width, height);
		context.renderer.setTransform(context.context, tm);
		context.renderer.color(context.context, 1.0f, 1.0f, 1.0f, 1.0f);
		context.renderer.gradient(context.context, 4, Gradient.REPEAT, img.getId());
	}
	
	private void drawUsingBlocks(DrawContext context, AffineTransform transform, Rectangle visibleArea) {
		AffineTransform itm;
		try {
			itm = transform.createInverse();
		} catch (NoninvertibleTransformException e) {
			System.out.println("Non inversible transform");
			return;
		}
		double xmin = Double.MAX_VALUE;
		double xmax = Double.MIN_VALUE;
		double ymin = Double.MAX_VALUE;
		double ymax = Double.MIN_VALUE;
		Point2D.Double p = new Point2D.Double();
		
		for(int y = 0; y < 2; y++) {
			for(int x = 0; x < 2; x++) {
				itm.transform(new Point2D.Double(
						visibleArea.x + visibleArea.width * x,
						visibleArea.y + visibleArea.height * y), p);
			
				xmin = Math.min(xmin, p.x);
				xmax = Math.max(xmax, p.x);
				ymin = Math.min(ymin, p.y);
				ymax = Math.max(ymax, p.y);
			}
		}

		xmin = Math.floor(xmin / width);
		xmax = Math.ceil(xmax / width);
		ymin = Math.floor(ymin / height);
		ymax = Math.ceil(ymax / height);

		for(int row = (int)ymin; row <= ymax; row++) {
			for(int col = (int)xmin; col <= xmax; col++) {
				drawBlock(context, transform, row, col, visibleArea);
			}
		}
	}
	
	private Rectangle getBlockBounds(AffineTransform transform, int row, int col) {
		double xmin = Double.MAX_VALUE;
		double xmax = Double.MIN_VALUE;
		double ymin = Double.MAX_VALUE;
		double ymax = Double.MIN_VALUE;
		Point2D.Double p = new Point2D.Double();
		
		for(int y = 0; y < 2; y++) {
			for(int x = 0; x < 2; x++) {
				transform.transform(new Point2D.Double(
						(x + col) * width, (y + row) * height), p);
				
				xmin = Math.min(xmin, p.x);
				xmax = Math.max(xmax, p.x);
				ymin = Math.min(ymin, p.y);
				ymax = Math.max(ymax, p.y);
			}
		}
		
		return new Rectangle((int)Math.floor(xmin) - 2,
				(int)Math.floor(ymin) - 2,
				(int)(Math.ceil(xmax) - Math.floor(xmin)) + 4,
				(int)(Math.ceil(ymax) - Math.floor(ymin)) + 4);
	}
	
	private void drawBlock(DrawContext context, AffineTransform transform, int row, int col, 
			Rectangle visibleArea) {
		double corners[] = {
				0, 0, 
				width, 0, 
				width, height, 
				0, height};
		Rectangle blockBounds = addSafetyMargin(getBlockBounds(transform, row, col));
		AffineTransform tm = (AffineTransform)transform.clone();
		
		tm.translate(col * width, row * height);

		if(!blockBounds.intersects(visibleArea))
			return;

		// Draw pattern
		int surfaceType = getSurfaceType(pattern.getDrawType(context, tm));
		
		if(context.renderer.beginSurface(context.context, blockBounds, surfaceType) == 0)
			return;
		pattern.draw(new DrawContext(context, surfaceType), tm, visibleArea);
		
		// Draw clip rectangle
		context.renderer.beginSurface(context.context, blockBounds, Renderer.SURFACE_TYPE_COLOR);
		context.renderer.setTransform(context.context, tm);
		context.renderer.color(context.context, 1.0f, 1.0f, 1.0f, 1.0f);
		context.renderer.setPolygonAntialiasingFilter(context.context, null);
		context.renderer.tessBegin(context.context);
		
		for(int i = 0; i < 4; i++) {
			context.renderer.tessVertex(context.context, 
					corners[i * 2], 
					corners[i * 2 + 1]);			
		}
		
		context.renderer.tessTriangle(context.context, 0, false, 1, false, 2, false);
		context.renderer.tessTriangle(context.context, 0, false, 2, false, 3, false);
		context.renderer.tessEnd(context.context);
		
		// Combine clip and pattern
		context.renderer.color(context.context, 1.0f, 1.0f, 1.0f, 1.0f);
		context.renderer.drawSurfaceAndClip(context.context);
	}
	
	protected void show() {
		super.show();
		if(pattern != null)
			pattern.addVisibleParent(this);
	}

	protected void hide() {
		super.hide();
		if(pattern != null)
			pattern.removeVisibleParent(this);
	}
	
	public void childChanged(Rectangle area) {
		changed(null);
	}	
}
